package common.servlet;

import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;

import javax.ws.rs.core.*;
import javax.ws.rs.core.Response.Status;

import msg.*;
import msg.visitors.*;

import org.json.*;
import org.w3c.dom.Node;

import user.User;

import common.extensions.*;
import common.logger.*;
import common.logger.Logger.LogLevel;

/**
 * An abstract type of resource for REST servlets
 * 
 * @author Gal Lalouche
 */
public class AbstractResource {
	private final static AtomicLong		counter				= new AtomicLong(0);
	/**
	 * Will return true for {@link EventMessage}, and false for {@link Message}
	 */
	protected static EventCataloguer	eventCataloguer		= new EventCataloguer();
	/**
	 * Returns a {@link JSONObject} of the message
	 */
	protected static MessageToJson		jsoner				= new MessageToJson();
	private static Predicate<Message>	isEventPredicate	= new IsEventPredicate();
	private static Predicate<Message>	isMessagePredicate	= new IsMessagePredicate();

	/**
	 * Creates a new instance
	 */
	protected AbstractResource() {
	}

	/**
	 * @param user The user
	 * @return a json of the user
	 */
	public static JSONObject userToJSON(User user) {
		JSONObject object = new JSONObject();
		object.put("username", user.getLogin());
		object.put("name", user.getName());
		return object;
	}

	/**
	 * @param e The exception that occurred
	 * @return The default response for a JSON object exception
	 */
	protected static Response handleJsonException(JSONException e) {
		return Response.status(400).entity(e.getMessage()).build();
	}

	/**
	 * @param e The exception that occurred
	 * @return The default response for any exception
	 */
	protected static Response handleException(Exception e) {
		if (e instanceof JSONException) {
			return handleJsonException((JSONException)e);
		}
		e.printStackTrace();
		long errorId = counter.getAndIncrement();
		CompositeLogger.getInstance().log(LogLevel.ERROR, "Error number: " + errorId);
		CompositeLogger.getInstance().log(LogLevel.ERROR, e.getMessage());
		return Response.serverError().status(Status.INTERNAL_SERVER_ERROR)
				.entity("Some error occured on the server(#" + errorId + ")").build();
	}

	/**
	 * @param e The exception that occurred
	 * @return The default response for a user-caused exception
	 */
	protected static Response handleUserException(Exception e) {
		return Response.serverError().status(Status.BAD_REQUEST).entity(e.getMessage()).build();
	}

	/**
	 * @return The default response for a missing cookie
	 */
	protected static Response missingCookie() {
		return Response.serverError().status(Status.UNAUTHORIZED).entity("bad or missing cookie").build();
	}

	/**
	 * @param msgs The messages to order
	 * @return A {@link JSONArray} of the messages, ordered by date, from latests to oldest
	 */
	protected static JSONArray msgsToOrderedArray(Collection<? extends Message> msgs) {
		Collection<Message> sorted = orderMessages(msgs);
		JSONArray array = new JSONArray();
		for (Message msg : sorted) {
			try {
				array.put(msg.accept(jsoner));
			} catch (Exception e) {
				throw new AssertionError(e);
			}
		}
		return array;
	}

	/**
	 * Orders the messages, s.t. the latest message appears first
	 * 
	 * @param msgs The messages to order
	 * @return The messages ordered from last to first, by time
	 */
	protected static Collection<Message> orderMessages(Collection<? extends Message> msgs) {
		return Extender.orderBy(msgs, new Function<Message, Long>() {
			@Override
			public Long calculate(Message element) {
				return element.getTime();
			}
		});
	}

	/**
	 * A predicate that returns true on Messages that are not events
	 * 
	 * @author Gal Lalouche
	 */
	private static final class IsMessagePredicate implements Predicate<Message> {
		@Override
		public Boolean calculate(Message element) {
			try {
				return element.accept(eventCataloguer) == false;
			} catch (Exception e) {
				throw new AssertionError(e);
			}
		}
	}

	/**
	 * A predicate that returns true on Messages that are events
	 * 
	 * @author Gal Lalouche
	 */
	private static final class IsEventPredicate implements Predicate<Message> {
		@Override
		public Boolean calculate(Message element) {
			try {
				return element.accept(eventCataloguer);
			} catch (Exception e) {
				throw new AssertionError(e);
			}
		}
	}

	/**
	 * @return A predicate that returns <code>true</code> only on {@link Message} that are not {@link EventMessage}
	 */
	protected static Predicate<Message> getMessagePredicate() {
		return isMessagePredicate;
	}

	/**
	 * @return A predicate that returns <code>true</code> only on {@link EventMessage}
	 */
	protected static Predicate<Message> getEventPredicate() {
		return isEventPredicate;
	}

	/**
	 * @param center The center of the map
	 * @param sw The bottom-left corner of the map
	 * @param ne The top-right corner of the map
	 * @return The maximum distance from the center to any coordinate in the rectangle create from sw, ne
	 */
	protected static double getRadius(Location center, Location sw, Location ne) {
		return Math.max(center.getDistanceTo(sw), center.getDistanceTo(ne));
	}

	/**
	 * @param xml The XML to send
	 * @param fileName The filename to send under
	 * @return The response
	 */
	protected static Response sendXML(Node xml, String fileName) {
		return Response.ok(xml).header("Content-Disposition", "attachment; filename=\"" + fileName + "\"")
				.build();
	}

	/**
	 * @param msg The message
	 * @return Return a {@link JSONObject} representation of the message
	 */
	protected static JSONObject toJSON(Message msg) {
		try {
			return msg.accept(jsoner);
		} catch (Exception e) {
			throw new AssertionError(e);
		}
	}

	/**
	 * @param msg The message
	 * @return <code>true</code> iff the message is an event
	 */
	protected static Boolean isEvent(Message msg) {
		try {
			return msg.accept(eventCataloguer);
		} catch (Exception e) {
			throw new AssertionError(e);
		}
	}
}
